/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package parser

import java.lang.Long.parseLong
import java.lang.Double.parseDouble
import scala.util.parsing.combinator.syntactical.StdTokenParsers
import scala.util.parsing.input.Reader
import scala.util.parsing.input.Positional
import Nodes._
import DataTypes._
import util.ParsersEx
import scala.util.control.Exception

/**
 * Parser
 */
object Parser extends StdTokenParsers with ParsersEx {

  type Tokens = Lexer.type
  val lexical = Lexer

  /**
   * Parses Sawzall Clone syntax.
   */
  def parse(reader: Reader[Char]): ParseResult[List[Stmt]] = root(new lexical.Scanner(reader))
  def parse(src: String): ParseResult[List[Stmt]]          = root(new lexical.Scanner(src))
  
  /* parts */
  def root      = phrase(rep(statement))
  def statement: Parser[Stmt] = ( exprStmt
                                | defVarStmt
                                | defTable 
                                | defType
                                | emitStmt
                                | proto
                                | ifStmt
                                | whileStmt
                                | forStmt
                                | whenStmt
                                | breakStmt
                                | continueStmt
                                | emptyStmt
                                | blockStmt
                                | returnStmt
                                )

  def exprStmt     = (expr <~ ";" ) ^^ ExprStmt
  def defVar       = (opt("static") ~ varNames ~< ":" ~ dataType ~ opt("=" ~> varInitializer) ) ^^@ DefVarStmt

  def varNames     = rep1sep(ident ^^@ Identifier, ",")
  def defVarStmt   = (defVar <~ ";") 
  def defTable     = (varNames ~< ":" ~ tableInfo ~< ";") ^^@ DefTableStmt
  def tableInfo    = ("table" ~> (ident ^^@ Identifier) ~ opt(parExpr) ~ rep(tableIndex) ~< "of" ~ labeledType ~ rep(tableOption)) ^^ {
    case aggr ~ arg ~ idxs ~ conType ~ opts => TableInfo(aggr, arg, idxs, conType, opts)
  }

  def defType      = ("type" ~> ident ~< "=" ~ dataType ~< ";") ^^@ DefType

  def emitStmt     = ("emit" ~> variable ~ emitIndexes ~< "<-" ~ expr ~ rep(emitOption) ~< ";") ^^@ EmitStmt
  def emitIndexes  = rep("[" ~> expr ~< "]")
  def proto        = ("proto" ~> stringLit) ^^@ ProtoStmt
  def ifStmt       = ("if" ~> parExpr ~ statement ~ opt("else" ~> statement)) ^^@ mkIfStmt
  def whileStmt    = ("while" ~> parExpr ~ statement) ^^@ mkWhileStmt
  def forStmt      = ("for" ~> "(" ~> forInit ~< ";" ~ opt(expr) ~< ";" ~ opt(expr) ~< ")" ~ statement) ^^@ mkForStmt
  def whenStmt     = ("when" ~> "(" ~> whenVarDefs ~ expr ~< ")" ~ statement ) ^^@ mkWhenStmt
  def breakStmt    = ("break" ~ ";") ^^^@ BreakStmt()
  def continueStmt = ("continue" ~ ";") ^^^@ ContinueStmt()
  def emptyStmt    = (";" ^^^@ EmptyStmt())
  def blockStmt    = ( "{" ~> rep(statement) <~ "}") ^^@ mkBlockStmt
  def returnStmt   = ( "return" ~> expr ~< ";") ^^@ ReturnStmt
  def forInit      = repsep(defVar | expr ^^ ExprStmt, ",")
  def whenVarDefs  = rep1(whenVarDef)
  def whenVarDef   = (varNames ~< ":" ~ whenMod ~ dataType ~< ";") ^^@ WhenDefVar
  def whenMod: Parser[WhenMod] = "some" ^^^@ SomeMod() | 
                                 "each" ^^^@ EachMod() | 
                                 "all"  ^^^@ AllMod()
  
  def tableOption = (ident ~ labeledType) ^^@ TableOption
  def emitOption  = (ident ~ expr)        ^^@ EmitOption
  
  def basicType  = ident ^^@ ImperfectType
  def arrayType  = ("array" ~> "of" ~> dataType) ^^@ ArrayType
  def mapType    = ("map" ~> rep1("[" ~> dataType <~ "]" ) ~< "of" ~ dataType) ^^@ { (ks, t) => ks.foldRight(t)(MapType) }
  def tupleType  = ( "{" ~> repsep((ident ~< ":" ~ dataType) ^^ { case n~t => (n, t) }, ",") <~ "}" ) ^^@ PureTupleType
  def dataType: Parser[DataType] = basicType | arrayType | mapType | tupleType | 
                                   (funcType ^^@ { (params, ret) => FunctionType(ret, params.map{_.dataType})})

  def tableIndex = "[" ~> labeledType <~ "]"
  def labeledType = (opt(ident <~ ":") ~ dataType) ^^@ { 
    case (Some(n), v) => Label(n, v)
    case (None, v)    => Label("", v)
  }

  def varInitializer: Parser[Initializer] = 
      ( expr                                                                                               ^^  ExprInitializer
      | ( "{" ~> repsep(varInitializer, ",") <~ "}")                                                       ^^@ ListInitializer
      | ( "{" ~> repsep((varInitializer~ (":" ~> varInitializer)) ^^ {case k~v => (k, v) }, ",") <~ "}" )  ^^@ MapInitializer
      )

  // Expressions
  
  def expr = assignExpr
  def parExpr = "(" ~> expr <~ ")"

  def binOp(op: String) = new ( (Expr, Expr) => Expr ) with Positional {
    def apply(x: Expr, y: Expr) = BinOp(op, x, y).setPos(pos)
  } 

  def assignExpr:       Parser[Expr] = condOrExpr  *  (("=" | "+=" | "-=" | "*=" | "/=" | "%=") ^^@ binOp ) 
  def condOrExpr:       Parser[Expr] = condAndExpr *  (("||")                                   ^^@ binOp )
  def condAndExpr:      Parser[Expr] = eqExpr      *  (("&&")                                   ^^@ binOp )
  def eqExpr:           Parser[Expr] = compExpr    *  (("==" | "!=" )                           ^^@ binOp )
  def compExpr:         Parser[Expr] = addExpr     *  (("<" | ">" | "<=" | ">=" )               ^^@ binOp )
  def addExpr :         Parser[Expr] = mulExpr     *  (("+" | "-")                              ^^@ binOp )
  def mulExpr:          Parser[Expr] = unaryExpr   *  (("*" | "/" | "%")                        ^^@ binOp )
  def unaryExpr:        Parser[Expr] = postfix | ((("+" | "-" | "!" ) ~ unaryExpr) ^^@ UnOp.apply)
  def postfix:          Parser[Expr] = (primary ~ rep(postfixOp)) ^^ {
    case p ~ op => op.foldLeft(p) { (a, f) => f(a) }
  }

  case class AppendPos(f :Expr => Expr) extends (Expr => Expr) with Positional {
    def apply(e: Expr): Expr = f(e).setPos(pos)
  }

  def postfixOp = ( "(" ~> argList <~")") ^^@ { args => AppendPos(FuncCall(_, args)) } | 
                  ( "[" ~> expr <~ "]" )  ^^@ { i    => AppendPos(BinOp("[]", _, i)) } | 
                  ( "." ~> ident)         ^^@ { mem  => AppendPos(MemAccOp(_, mem))  } |
                  ( "++" | "--")          ^^@ { op   => AppendPos(UnOp(op, _)) }

  def primary: Parser[Expr] = ( parExpr
                              | literal
                              | variable
                              )
  def variable: Parser[Variable] = ident ^^@ Variable
                
  def argList = repsep(expr, ",")

  /**
   * Converts parse result using the function which may throw an exception.
   */
 def maybeConvert[T, U](f: T => U)(src: T): Parser[U] = {
    Exception.allCatch.either(f(src)) match {
      case Left(e)  => err(e.getMessage)
      case Right(c) => success(c)
    }
  }

  def literal = ( stringLit ^^@ { StringLit(_) }
                | octLit    >> maybeConvert(parseLong(_,  8))_ ^^@ { IntLit(_) }
                | hexLit    >> maybeConvert(parseLong(_, 16))_ ^^@ { IntLit(_) }
                | decLit    >> maybeConvert(parseLong(_, 10))_ ^^@ { IntLit(_) }
                | floatLit  >> maybeConvert(parseDouble(_))_   ^^@ { FloatLit(_) }
                | charLit   ^^@ { x => IntLit(x.charAt(0)) }
                | funcLit
                )

  def funcLit  = (funcType ~< "{" ~ rep(statement) ~< "}") ^^@ { (args, ret, stmts) => 
    FuncLit(ret, args, stmts)
  }
  def funcType = "function" ~> "(" ~> repsep(labeledType, ",") ~< ")" ~< ":" ~ dataType
  
  def octLit   = elem("octal number literal",          _.isInstanceOf[lexical.OctLit])   ^^ (_.chars)
  def hexLit   = elem("hex number literal",            _.isInstanceOf[lexical.HexLit])   ^^ (_.chars)
  def decLit   = elem("number literal",                _.isInstanceOf[lexical.DecLit])   ^^ (_.chars)
  def floatLit = elem("floating point number literal", _.isInstanceOf[lexical.FloatLit]) ^^ (_.chars)
  def charLit  = elem("charactor literal",             _.isInstanceOf[lexical.CharLit])  ^^ (_.chars)
}
